﻿using System;
using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;

namespace DG.Common.EF.Logging
{
    public class EFExceptionLogger<TLogId, TLogCampoId, TLogKeyId, TUsuarioID, TLogTipoId>
        where TLogId : struct  , IComparable<TLogId>
        where TLogCampoId : struct  , IComparable<TLogCampoId>
        where TLogKeyId : struct  , IComparable<TLogKeyId>
        where TUsuarioID : struct  , IComparable<TUsuarioID>
        where TLogTipoId : struct  , IComparable
    {
        public AppDomain AppDomain { get; private set; }
        public Func<string> GetUserMessage { get; private set; }
        public Action<ILog<TLogId, TLogCampoId, TLogKeyId, TUsuarioID, TLogTipoId>, Exception> AfterLogCallBack { get;
            private set; }

        public Func<IEFLoggable<TLogId, TLogCampoId, TLogKeyId, TUsuarioID, TLogTipoId>> EFLoggableFactory { get; set; }

        public EFExceptionLogger(
            AppDomain appDomain, 
            Func<IEFLoggable<TLogId, TLogCampoId, TLogKeyId, TUsuarioID, TLogTipoId>> efLoggableFactory,
            Action<ILog<TLogId, TLogCampoId, TLogKeyId, TUsuarioID, TLogTipoId>, Exception> afterLogCallBack = null
            , Func<string> getUserMessage = null)

        {
            AppDomain = appDomain;
            GetUserMessage = getUserMessage;
            AfterLogCallBack = afterLogCallBack;
            EFLoggableFactory = efLoggableFactory;
            
            //TODO: Forms
            //Application.ThreadException += (s, e) => doLogForException(e.Exception);
            //if (System.Windows.Application.Current != null)
            //{
            //    System.Windows.Application.Current.DispatcherUnhandledException += (s, e) =>
            //    {
            //        doLogForException(e.Exception);
            //        e.Handled = true;
            //    };
            //}
        }
        
        public ILog<TLogId, TLogCampoId, TLogKeyId, TUsuarioID, TLogTipoId> RegisterException(Exception e)            
        {
            if (e == null) throw new ArgumentNullException();

            var db = EFLoggableFactory();

            try
            {
                var log = db.CreateLog();

                log.LogTipoId = db.ErrorId;

                var maxId = db.GetNextLogID();

                string version;
                string pcname;
                string ip;
                var user = LogExtensions.FillInfosToLog(out version, out pcname, out ip);

                maxId = db.GetIncrementId(maxId);

                log.LogId = maxId;
                log.Hora = DateTime.Now;
                log.UsuarioId = db.UsuarioID;
                log.UsuarioNome = db.UsuarioNome;

                log.MachineName = pcname;
                log.WindowsUserName = user;
                log.OperatingSystem = version;
                log.IP = ip;

                log.TipoDoErro = e.GetType().FullName;


                var logDbSet = db.Context.Set(log.GetType());
                logDbSet.Add(log);

                var currentError = e;
                var allErrorMsgs = new List<string>();
                var errorMsgs = new List<string>();

                while (currentError != null)
                {
                    var currentErroMsg = currentError.Message;
                    try
                    {
                        var dbEntityValidationException = e as DbEntityValidationException;
                        if (dbEntityValidationException != null)
                        {
                            currentErroMsg += Environment.NewLine
                                + "Entity Validation Exceptions:"
                                + Environment.NewLine;
                            foreach (var error in dbEntityValidationException.EntityValidationErrors)
                            {
                                if (error.Entry.Entity != null)
                                {
                                    currentErroMsg += Environment.NewLine + "Entidade: " + error.Entry.Entity + Environment.NewLine;
                                }
                                foreach (var dbValidationError in error.ValidationErrors)
                                {
                                    object currentValue = "[Indeterminado]";
                                    try
                                    {
                                        if (error.Entry.CurrentValues.PropertyNames.Contains(dbValidationError.PropertyName))
                                        {
                                            currentValue = error.Entry.CurrentValues[dbValidationError.PropertyName];
                                        }
                                        else if (error.Entry.Entity != null)
                                        {
                                            var prop = error.Entry.Entity.GetType().GetProperty(dbValidationError.PropertyName);
                                            if (prop != null)
                                            {
                                                currentValue = prop.GetValue(error.Entry.Entity, null);
                                            }
                                        }
                                    }
                                    catch (Exception exception)
                                    {
                                        Console.WriteLine(exception);
                                    }
                                    
                                        
                                    currentErroMsg += string.Format("\t{0}\n\t\tValor Atual: {1}\n\t\tErro de Validação:{2}\n",
                                        dbValidationError.PropertyName
                                        , currentValue ?? "[Nulo]"
                                        ,dbValidationError.ErrorMessage);
                                }
                            }
                        }
                    }
                    catch (Exception)
                    {
                        Console.WriteLine("Não foi possível obter os dados de validações errôneas");
                    }

                    errorMsgs.Add(currentErroMsg);
                    allErrorMsgs.Add(currentErroMsg + Environment.NewLine + Environment.NewLine + currentError.StackTrace);
                    currentError = currentError.InnerException;
                }
                var msgBuilder = new StringBuilder();


                msgBuilder.AppendLine("Aplicação: " +  Assembly.GetEntryAssembly().ManifestModule.Name);
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();                
                msgBuilder.AppendLine("Usuário: " + ((log.UsuarioNome != "") ? log.UsuarioNome : "Nenhum"));
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();

                if (GetUserMessage != null)
                {
                    msgBuilder.AppendLine("Mesangem do Usuário:");
                    msgBuilder.AppendLine();
                    msgBuilder.AppendLine();
                    msgBuilder.AppendLine(GetUserMessage());
                    msgBuilder.AppendLine();
                    msgBuilder.AppendLine();
                }
                
                msgBuilder.AppendLine("Erros Resumidos");
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();
                msgBuilder.AppendLine(string.Join(Environment.NewLine, errorMsgs));
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();
                msgBuilder.AppendLine("Erros Detalhados");
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();
                msgBuilder.AppendLine(string.Join(Environment.NewLine, allErrorMsgs));
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();
                msgBuilder.AppendLine("StackTrace do Ambiente");
                msgBuilder.AppendLine();
                msgBuilder.AppendLine();
                msgBuilder.AppendLine(Environment.StackTrace);
                

                log.DescricaoDoErro = msgBuilder.ToString();

                db.BaseSaveChanges();

                return log;
            }
            catch (Exception ex)
            {

                Console.WriteLine("Não foi possível registrar o log: " + ex.Message);
            }

            return null;
        }

        public void DoLog(Exception e)
        {
            var log = RegisterException(e);

            if (AfterLogCallBack != null) AfterLogCallBack.Invoke(log, e);
        }
    }
}
